﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Xml.XPath;

namespace Smarly.Ebook
{
	public class TextItem1
	{
		public TextItem1()
		{
			Items = new List<TextItem1>();
		}

		public Tag Tag { get; set; }
		public long Id { get; set; }
		public long AskingTextId { get; set; }
		public string OriginalText { get; set; }

		public List<TextItem1> Items { get; set; }

		public bool NeedOriginal { get; set; }

		public Properties ExtData { get; set; }

		public long ChapterId { get; set; }
		public long BookId { get; set; }
		public long Order { get; set; }
		public int Level { get; set; }
		public long? ParentId { get; set; }

//		public Chapter Chapters { get; set; }

		public class Properties
		{
			public string Src { get; set; }
			public string Class { get; set; }
			public long SrcChapter { get; set; }
		}
	}

	public class TextItemParser
	{
		protected static readonly Regex Br = new Regex(@"(<br[^>]*/{0,1}>)", RegexOptions.IgnoreCase);


		public static TextItem1 Parse(string str, bool saveBlock = false, bool original = true)
		{
			str = Br.Replace(str, "[[br]]");
			XDocument doc = XDocument.Parse(str, LoadOptions.PreserveWhitespace);
			string postfix = string.Empty;
			var result = GetBlock(AddTempIds(doc.Root, saveBlock), saveBlock, null, original, ref postfix);
			result.Items.Add(result);

			return result;
		}

		private static XElement AddTempIds(XElement root, bool saveBlock)
		{
			long tempid = -1;
			foreach (var el in root.XPathSelectElements(@"//*[not(@id)]"))
			{
				el.SetAttributeValue("id", /*saveBlock ? new TextItem1().Save().Id :*/ tempid--);
			}
			return root;
		}




		private static TextItem1.Properties ParseAttributes(XElement el, Tag tag)
		{
			TextItem1.Properties result = null;

			switch (tag)
			{
				case Tag.img:
					var attribute = el.SingleAttributeValue("src");

					try
					{
						new Uri(attribute);
					}
					catch
					{
						attribute = "/" + attribute.TrimStart('.', '/');
					}
					result = new TextItem1.Properties
					{
						Src = attribute,
					};
					try
					{
						string @class = el.SingleOrDefaultAttributeValue("class");
						result = result ?? new TextItem1.Properties();
						result.Class = @class;
					}
					catch
					{
					}

					break;
				case Tag.pre:
					try
					{
						var @class = el.SingleOrDefaultAttributeValue("class");
						result = new TextItem1.Properties
						{
							Class = @class,
						};
					}
					catch
					{
					}
					break;
				case Tag.a:
					result = new TextItem1.Properties()
					{
						Src = el.SingleAttributeValue("href").TrimStart('#'),
					};
					break;

			}
			return result;
		}

		private static string InnerXmlValue(XElement root)
		{
			if (root.Value == string.Empty)
				return string.Empty;

			var doc = new XDocument(root).Root;
			doc.Value = ";";
			var array = doc.ToString(SaveOptions.DisableFormatting).Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
			string result = root.ToString(SaveOptions.DisableFormatting);
			result = result.Substring(0, result.Length - array[1].Length).Substring(array[0].Length);
			return result;
		}


		private static readonly Regex EndSpaces = new Regex(@"(\u0020|\u00A0){1,}$", RegexOptions.None);

		private static TextItem1 GetBlock(XElement root, bool saveBlock, List<TextItem1> list, bool original, ref string postFix)
		{
			var result = new TextItem1() { Id = root.ElementId() };
			result.Items = list ?? new List<TextItem1>();

			try
			{
				result.Tag = (Tag)Enum.Parse(typeof(Tag), root.Name.ToString());
			}
			catch
			{
				switch (root.Name.ToString())
				{
					case "i":
						result.Tag = Tag.em;
						break;
					case "b":
						result.Tag = Tag.strong;
						break;
					default:
						throw;
				}
			}
			result.ExtData = ParseAttributes(root, result.Tag);

			string rootStr = InnerXmlValue(root);
			foreach (XElement node in root.Elements())
			{
				TextItem1 child = GetBlock(node, saveBlock, result.Items, original, ref postFix);
				result.Items.Add(child);

				rootStr = rootStr.Replace(node.ToString(SaveOptions.DisableFormatting), "<text id='" + child.Id + "'/>" + postFix);
			}

			postFix = EndSpaces.Match(rootStr).Value;
			rootStr = EndSpaces.Replace(rootStr, string.Empty);

			switch (result.Tag)
			{
				case Tag.pre:
					rootStr = rootStr.TrimStart('\r', '\n').Replace(" ", " ").Replace("    ", "\t").TrimEnd();
					break;
			}


			result.OriginalText = rootStr.Replace("[[br]]", "<br />");

			result.OriginalText = TrimSpaces(result.OriginalText);

			result.NeedOriginal = original;

			/*			if (saveBlock)
							result.Save();*/

			return result;
		}

		protected static readonly Regex NonBreakingSpaces = new Regex(@"\u0020*\u00A0+\u0020*", RegexOptions.None);
		protected static readonly Regex BeginSpaces = new Regex(@"^(\u0020|\u00A0){1,}", RegexOptions.None);
		protected static readonly Regex DoubleSpaces = new Regex(@"\u0020+", RegexOptions.None);

		private static string TrimSpaces(string str)
		{
			str = NonBreakingSpaces.Replace(str, /*nbs*/" ");
			str = BeginSpaces.Replace(str, string.Empty);
			/*
						if (DoubleSpaces.Matches(str).Cast<Match>().Any(match => string.IsNullOrWhiteSpace(match.Value)))
						{
							var l = DoubleSpaces.Matches(str).Cast<Match>().Where(match => string.IsNullOrWhiteSpace(match.Value)).ToList();
							throw new ApplicationException("Херня ваш \\s");
						}
			*/

			str = DoubleSpaces.Replace(str, " ");
			return str;
		}

	}

	public static class LinqXmlExtentions
	{
		public static long ElementId(this XElement element)
		{
			return long.Parse(element.SingleAttributeValue("id"));
		}

		//todo не нравится частое выполнение на создание объекта для того что бы только вытащить string в long
		public static long ElementId(this string xml)
		{
			return XDocument.Parse(xml, LoadOptions.PreserveWhitespace).Root.ElementId();
		}

		public static string SingleAttributeValue(this XElement element, string name)
		{
			return element.Attributes(name).Single().Value;
		}

		public static string SingleOrDefaultAttributeValue(this XElement element, string name)
		{
			var attribute = element.Attributes(name).SingleOrDefault();
			return attribute != null ? attribute.Value : null;
		}
	}
}
